using System;
using System.Collections.Generic;
using OhioState.Graphics;

namespace OhioState.Math
{
    //
    // Spline - Keeps track of any number of knots placed on a spline
    //   Internal representation uses an sorted List of Knots
    //   External representation is a queue that you can move through from front to back
    //   Positions range from 0.0 to 1.0
    //
    [Serializable()]
    public class Spline
    {
        private List<Knot> l;
        //[NonSerialized()] private int pos;

        //
        // Constructors
        //  Default constructor - used if you want to programmatically build a spline from scratch
        //
        public Spline()
        {
            l = new List<Knot>();
            //pos = 0;
        }
        //
        // This constructor is prefered because it gaurantees that there will always be at least one knot
        //  in the spline
        //
        public Spline(Knot k)
        {
            l = new List<Knot>();
            //pos = 0;
            AddKnot(k);
        }

        public Knot this[int index]
        {
            get
            {
                if (l.Count == 0)
                    throw new Exception("There are no knots");
                if (index >= l.Count)
                    throw new Exception("Out of spline range"); 

                l.Sort();
                return l[index];
            }
        }

        public int Count
        {
            get { return l.Count; }
        }

        //
        // Add a knot
        //
		public void AddKnot(Knot k)
		{
			l.Add(k);
			l.Sort();
		}

        // 
        // Set the current position back to the begining (0.0)
        //
		//public void Restart()
		//{
		//	l.Sort();
		//	pos = 0;
		//}
        //
        // Set the current position to 0 and return the first knot
        //
		//public Knot GetFirstKnot()
		//{
		//	if(l.Count > 0)
		//	{
		//		l.Sort();
		//		pos = 1;
		//		return (Knot)l[0];
		//	}
		//	throw new Exception("There are no knots");  
		//}
        //
        // Increment the current position and return the next Knot
        //
		//public Knot GetNextKnot()
		//{
		//	pos++;
		//	if(pos-1 < l.Count)
		//		return (Knot)l[pos-1];
		//	
		//	throw new Exception("There is no next knot");  
		//}
        //
        // Determine whether there is a next knot
        //  i.e. are you at the last knot of the spline
        //
		//public bool NextKnot()
		//{
		//	return (pos < l.Count);
		//}

        //
        // Remove the given knot from the spline
        //
		public void RemoveKnot(Knot k)
		{
			if(l.Count > 1)
			{
				l.Remove(k);
			}
		}

        //
        // Calculate the value of the spline at the given position
        //   If no knot is found at this position, linear interpolation
        //    is used with the two nearest neighbors to calculate a value
        //
		public Knot Evaluate(float pos)
		{
			Knot knot_a, knot_b;

			if(l.Count > 0)
			{
				knot_a = (Knot)l[0];
				if(pos <= knot_a.Pos)
					return knot_a.NewKnot(pos, knot_a.Obj);
				for(int i = 1; i < l.Count; i++)
				{
					knot_b = (Knot)l[i];
					if((pos > knot_a.Pos) && (pos <= knot_b.Pos))
						return knot_a.Interpolate(pos, knot_b);
					knot_a = knot_b;
				}
				return knot_a.NewKnot(pos, knot_a.Obj);
			}
			throw new Exception("There are no knots");
		}
	}


    //
    // AlphaKnot - a spline knot meant to store only alpha values
    //
	[Serializable()]
	public class AlphaKnot: ColorKnot
	{
        public AlphaKnot()
        {
            p = 0.0f;
            o = new OSUColor();
        }

		public AlphaKnot(float pos, float a)
		{
			if((pos < 0.0) || (pos > 1.0))
				throw new ArgumentException("pos is not in the range 0.0 to 1.0"); 
			p = pos;
			o = new OSUColor(a, 1.0f-a, 1.0f-a, 1.0f-a);
		}

		public override Knot Interpolate(float pos, Knot k)
		{
			float factor = (pos - p)/(k.Pos - p);
			AlphaKnot knot = new AlphaKnot(pos, ((AlphaKnot)k).Alpha * factor + Alpha * (1.0f - factor));
			return knot;
		}

		public override Knot NewKnot(float pos, Object obj)
		{
            AlphaKnot k = new AlphaKnot(pos, ((OSUColor)obj).A);
			return k;
		}

		public float Alpha
		{
			get{ return ((OSUColor)o).A; }
			set
			{
                ((OSUColor)o).A = value;
                ((OSUColor)o).R = 1.0f - value;
                ((OSUColor)o).B = 1.0f - value;
                ((OSUColor)o).G = 1.0f - value;
			}
		}

        public override OSUColor Color
		{
			get{ return (OSUColor)o; }
			set{ Alpha = value.A; }
		}
	}


    //
    // ColorKnot - a spline knot meant only to hold true color values
    //
	[Serializable()]
	public class ColorKnot: Knot
	{
		private bool hsv;

		public ColorKnot()
		{
			p = 0.0f;
			o = new OSUColor(0.0f, 0.0f, 0.0f);
			hsv = false;
		}

		public ColorKnot(float pos, OSUColor c)
		{
			if((pos < 0.0) || (pos > 1.0))
				throw new ArgumentException("pos is not in the range 0.0 to 1.0"); 
			p = pos;
			o = c;
			hsv = false;
		}
		public ColorKnot(float pos, float r, float g, float b)
		{
			if((pos < 0.0) || (pos > 1.0))
				throw new ArgumentException("pos is not in the range 0.0 to 1.0"); 
			p = pos;
            o = new OSUColor(r, g, b);
			hsv = false;
		}
		public ColorKnot(float pos, float a, float r, float g, float b)
		{
			if((pos < 0.0) || (pos > 1.0))
				throw new ArgumentException("pos is not in the range 0.0 to 1.0"); 
			p = pos;
            o = new OSUColor(a, r, g, b);
			hsv = false;
		}

        //
        // Interpolate between this Knot and Knot k based on the position in the spline
        //  The process for this is different in HSV and RGV space.
        //  Returns a new ColorKnot
        //
		public override Knot Interpolate(float pos, Knot k)
		{
			float factor = (pos - p)/(k.Pos - p);
            OSUColor c = new OSUColor();
			c.A = ((ColorKnot)k).Color.A * factor + Color.A * (1.0f - factor);
			if(hsv)
			{
                //
                // We need to check for a saturation of 0 in either color.  If either has
                // a saturation of 0, the we need to ignore that color's hue.
                //
                float s1 = ((ColorKnot)k).Color.S;
                float s2 = Color.S;
				c.V = ((ColorKnot)k).Color.V * factor + Color.V * (1.0f - factor);
				c.S = s1 * factor + s2 * (1.0f - factor);
                if(s1 == 0.0f)
                    c.H = Color.H;
                else if(s2 == 0.0f)
                    c.H = ((ColorKnot)k).Color.H;
                else
                    c.H = ((ColorKnot)k).Color.H * factor + Color.H * (1.0f - factor);
				//c.SetByHSV(((ColorKnot)k).Color.H * factor + Color.H * (1.0f - factor),
				//	((ColorKnot)k).Color.S * factor + Color.S * (1.0f - factor),
				//	((ColorKnot)k).Color.V * factor + Color.V * (1.0f - factor));
			}
			else
			{
				c.R = ((ColorKnot)k).Color.R * factor + Color.R * (1.0f - factor);
				c.G = ((ColorKnot)k).Color.G * factor + Color.G * (1.0f - factor);
				c.B = ((ColorKnot)k).Color.B * factor + Color.B * (1.0f - factor);
			}
			ColorKnot knot = new ColorKnot(pos, c);
			return knot;
		}
		public override Knot NewKnot(float pos, Object obj)
		{
            ColorKnot k = new ColorKnot(pos, ((OSUColor)obj).A, ((OSUColor)obj).R, ((OSUColor)obj).G, ((OSUColor)obj).B);
			return k;
		}

        public virtual OSUColor Color
		{
			get{ return (OSUColor)o; }
			set{ o = value; }
		}
        //
        // Get/set whether this knot is in HSV mode
        //  This tag is needed because interpolation is different in each color space
        //
		public bool HSV
		{
			get{ return hsv; }
			set{ hsv = value; }
		}
	}


    //
    // Knot - an abstract Knot that lies on a spline
    //
	[Serializable()]
    public abstract class Knot : IComparable
	{
		protected float p;
		protected Object o;

		//
		// IComparable.CompareTo implementation.
		//  Simply compare positions on the spline
        //
		public int CompareTo(object obj)
		{
			if(obj is Knot) 
			{
				Knot temp = (Knot) obj;
				return 2*p.CompareTo(temp.Pos)-1;
			}
			throw new ArgumentException("object is not a Knot");   
		}

        //
        // Abstract Interpolate - interpolate between this Knot and Knot k
        // Concrete knots will have to specify how they are interpolated
        //
        public abstract Knot Interpolate(float pos, Knot k);
        //
        // Abstract NewKnot - create a new Knot with the given values
        // Concrete knots will have to specify how they are created
        //  since you cannot create an abstract object
        //
		public abstract Knot NewKnot(float pos, Object obj);
		public float Pos
		 {
			 get{ return p; }
			 set
			 {
				 if((value < 0.0) || (value > 1.0))
					 throw new ArgumentException("pos is not in the range 0.0 to 1.0");
				 p = value;
			 }
		}
		
		public Object Obj
		{
			get{ return o; }
			set{ o = value; }
		}
	}
}
